﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace Framework.Manager
{
    public class InputManager : ManagerBase<InputManager>
    {
        private InputManager() { }

        private enum InputType
        {
            Key = 1,
            KeyDown,
            KeyUp,
        }

        private class InputData
        {
            public InputData(KeyCode pKeyCode, InputType pInputType)
            {
                mKeyCode = pKeyCode;
                mInputType = pInputType;
            }

            public KeyCode mKeyCode;
            public InputType mInputType;
            public List<Action<KeyCode>> mCallbackList = new List<Action<KeyCode>>();
        }

        private Dictionary<string, InputData> mInputDataDictionary = new Dictionary<string, InputData>();
        private Dictionary<string, InputData> mUnregisterDictionary = new Dictionary<string, InputData>();

        #region Register
        public void RegisterKey(KeyCode pKeyCode, Action<KeyCode> pCallback)
        {
            RegisterInput(pKeyCode, InputType.Key, pCallback);
        }

        public void RegisterKeyDown(KeyCode pKeyCode, Action<KeyCode> pCallback)
        {
            RegisterInput(pKeyCode, InputType.KeyDown, pCallback);
        }

        public void RegisterKeyUp(KeyCode pKeyCode, Action<KeyCode> pCallback)
        {
            RegisterInput(pKeyCode, InputType.KeyUp, pCallback);
        }

        private void RegisterInput(KeyCode pKeyCode, InputType pInputType, Action<KeyCode> pCallback)
        {
            string inputKey = GenerateInputKey(pKeyCode, pInputType);
            InputData inputData = null;
            if (!mInputDataDictionary.TryGetValue(inputKey, out inputData))
            {
                inputData = new InputData(pKeyCode, pInputType);
                mInputDataDictionary.Add(inputKey, inputData);
            }

            if (pCallback != null && !inputData.mCallbackList.Contains(pCallback))
                inputData.mCallbackList.Add(pCallback);
        }
        #endregion

        #region UnregisterKey
        public void UnregisterKey(KeyCode pKeyCode, Action<KeyCode> pCallback)
        {
            UnregisterInput(pKeyCode, InputType.Key, pCallback);
        }

        public void UnregisterKeyDown(KeyCode pKeyCode, Action<KeyCode> pCallback)
        {
            UnregisterInput(pKeyCode, InputType.KeyDown, pCallback);
        }

        public void UnregisterKeyUp(KeyCode pKeyCode, Action<KeyCode> pCallback)
        {
            UnregisterInput(pKeyCode, InputType.KeyUp, pCallback);
        }

        private void UnregisterInput(KeyCode pKeyCode, InputType pInputType, Action<KeyCode> pCallback)
        {
            string inputKey = GenerateInputKey(pKeyCode, pInputType);
            InputData inputData = null;
            if (!mUnregisterDictionary.TryGetValue(inputKey, out inputData))
            {
                inputData = new InputData(pKeyCode, pInputType);
                mUnregisterDictionary.Add(inputKey, inputData);
            }

            if (pCallback != null && !inputData.mCallbackList.Contains(pCallback))
                inputData.mCallbackList.Add(pCallback);
        }
        #endregion

        private string GenerateInputKey(KeyCode pKeyCode, InputType pInputType)
        {
            string inputKey = string.Format("{0}_{1}", pKeyCode.ToString(), pInputType.ToString());
            return inputKey;
        }

        private void Update()
        {
            TraversalInputDictionary();
            UnregisterInput();
        }

        private void TraversalInputDictionary()
        {
            if (mInputDataDictionary.Count > 0)
            {
                foreach (KeyValuePair<string, InputData> kvp in mInputDataDictionary)
                {
                    switch (kvp.Value.mInputType)
                    {
                        case InputType.Key:
                            if (Input.GetKey(kvp.Value.mKeyCode))
                                ResponseInput(kvp.Value);
                            break;
                        case InputType.KeyDown:
                            if (Input.GetKeyDown(kvp.Value.mKeyCode))
                                ResponseInput(kvp.Value);
                            break;
                        case InputType.KeyUp:
                            if (Input.GetKeyUp(kvp.Value.mKeyCode))
                                ResponseInput(kvp.Value);
                            break;
                    }
                }
            }
        }

        private void ResponseInput(InputData pInputData)
        {
            if (pInputData.mCallbackList.Count > 0)
            {
                foreach (Action<KeyCode> action in pInputData.mCallbackList)
                {
                    action.Invoke(pInputData.mKeyCode);
                }
            }
        }

        private void UnregisterInput()
        {
            if (mUnregisterDictionary.Count > 0)
            {
                foreach (KeyValuePair<string, InputData> kvp in mUnregisterDictionary)
                {
                    if (mInputDataDictionary.ContainsKey(kvp.Key))
                    {
                        InputData inputData = mInputDataDictionary[kvp.Key];
                        foreach (Action<KeyCode> callback in kvp.Value.mCallbackList)
                        {
                            if (inputData.mCallbackList.Contains(callback))
                                inputData.mCallbackList.Remove(callback);
                        }

                        if (inputData.mCallbackList.Count == 0)
                            mInputDataDictionary.Remove(kvp.Key);
                    }
                }

                mUnregisterDictionary.Clear();
            }
        }
    }
}